﻿using BEPUphysics;
using OpenTK;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace LDLib
{
    /// <summary>
    /// The scene graph class.
    /// </summary>
    public class Scene
    {
        /// <summary>
        /// Delegate for pre-update
        /// </summary>
        /// <param name="dt">Time since the last call</param>
        public delegate void DelPreUpdate(double dt);

        /// <summary>
        /// Delegate for post-update
        /// </summary>
        /// <param name="dt">Time since the last call</param>
        public delegate void DelPostUpdate(double dt);

        public DelPreUpdate PreUpdate;
        public DelPostUpdate PostUpdate;

        public static Space Space { get; private set; }

        /// <summary>
        /// Name of the scene
        /// </summary>
        public string Name { get; set; }

        /// <summary>
        /// List of active game objects in the scene
        /// </summary>
        public List<GameObject> GameObjects { get; private set; }

        /// <summary>
        /// List of game objects to be added before the next update cycle
        /// </summary>
        private List<GameObject> GameObjectsToAdd { get; set; }

        /// <summary>
        /// List of game objects to be removed before the next update cycle
        /// </summary>
        private List<GameObject> GameObjectsToRemove { get; set; }
        

        /// <summary>
        /// Constructs a new scene
        /// </summary>
        /// <param name="name">Name of the scene</param>
        public Scene(string name)
        {
            this.Name = name;
            this.GameObjects = new List<GameObject>();
            this.GameObjectsToAdd = new List<GameObject>();
            this.GameObjectsToRemove = new List<GameObject>();
            if(Space == null)
            {
                Space = new Space();
                Space.ForceUpdater.Gravity = new Vector3(0f, -10f, 0f);
            }
        }

        /// <summary>
        /// Adds a game object to the add queue
        /// </summary>
        /// <param name="go">Game object to add to the scene</param>
        public void AddGameObject(GameObject go)
        {
            if (!GameObjects.Contains(go) && !GameObjectsToAdd.Contains(go))
            {
                GameObjectsToAdd.Add(go);
            }
        }

        /// <summary>
        /// Adds a game object to the remove queue
        /// </summary>
        /// <param name="go">Game object to remove from the scene</param>
        public void RemoveGameObject(GameObject go)
        {
            if(GameObjects.Contains(go) && !GameObjectsToRemove.Contains(go))
            {
                GameObjectsToRemove.Add(go);
            }
        }

        /// <summary>
        /// Updates the scene and all the game objects in it.
        /// </summary>
        /// <param name="dt">Time since the last call</param>
        internal void Update(double dt)
        {
            // Invoke pre-updates
            if (PreUpdate != null) PreUpdate.Invoke(dt);

            // Add the game objects from the add queue
            foreach (GameObject go in GameObjectsToAdd) GameObjects.Add(go);

            // Remove the game objects from the remove queue
            foreach (GameObject go in GameObjectsToRemove) GameObjects.Remove(go);
            
            // Clear the queues
            GameObjectsToAdd.Clear();
            GameObjectsToRemove.Clear();

            // Update physics
            Space.Update((float)dt);

            // Update camera
            Camera.Main.UpdateFrustum();

            // Update the game objects
            foreach (GameObject go in GameObjects) go.Update(dt);

            // Invoke post-updates
            if (PostUpdate != null) PostUpdate.Invoke(dt);
        }

        /// <summary>
        /// Renders the scene and all the game objects in it.
        /// </summary>
        /// <param name="dt">Time since the last call</param>
        internal void Render(double dt)
        {
            foreach (GameObject go in GameObjects) go.Render(dt);

            RenderBatch.Render();
        }
    }
}
